//  Raytracer Assignment week 4 
//	by Zse Sun and Gerard Knap 
//
//  Framework for a raytracer
//  File: raytracer.cpp
//
//  Created for the Computer Science course "Introduction Computer Graphics"
//  taught at the University of Groningen by Tobias Isenberg.
//
//  Author: Maarten Everts
//	  Zhe Sun
//
//  This framework is inspired by and uses code of the raytracer framework of 
//  Bert Freudenberg that can be found at
//  http://isgwww.cs.uni-magdeburg.de/graphik/lehre/cg2/projekt/rtprojekt.html 
//

#include "raytracer.h"
#include "object.h"
#include "geometric.h"
#include "material.h"
#include "light.h"
#include "image.h"
#include "yaml/yaml.h"
#include <ctype.h>
#include <fstream>
#include <assert.h>

// Functions to ease reading from YAML input
void operator >>(const YAML::Node& node, Triple& t);
Triple parseTriple(const YAML::Node& node);

void operator >>(const YAML::Node& node, Triple& t) {
    assert(node.size() == 3);
    node[0] >> t.x;
    node[1] >> t.y;
    node[2] >> t.z;
}

Triple parseTriple(const YAML::Node& node) {
    Triple t;
    node[0] >> t.x;
    node[1] >> t.y;
    node[2] >> t.z;
    return t;
}

Material* Raytracer::parseMaterial(const YAML::Node& node) {
    Material *m = new Material();
    node["color"] >> m->color;
    node["ka"] >> m->ka;
    node["kd"] >> m->kd;
    node["ks"] >> m->ks;
    node["n"] >> m->n;
    return m;
}

Object* Raytracer::parseObject(const YAML::Node& node) {
    Object *returnObject = NULL;
    std::string objectType;
    node["type"] >> objectType;

    if (objectType == "sphere") {
        Point pos;
        node["position"] >> pos;
        double r;
        node["radius"] >> r;
        Sphere *sphere = new Sphere(pos, r);
        returnObject = sphere;
    }

    if (objectType == "plan") {
        Point pos;
        node["position"] >> pos;
        Point normalPos;
        node["normal"] >> normalPos;
        Plan *plan = new Plan(pos, normalPos);
        returnObject = plan;
    }

    if (objectType == "triangle") {
        Point v1, v2, v3;
        node["v1"] >> v1;
        node["v2"] >> v2;
        node["v3"] >> v3;
        Triangle *triangle = new Triangle(v1, v2, v3);
        returnObject = triangle;
    }

    if (objectType == "cylinder") {
        Point pos;
        node["position"] >> pos;
        double r;
        node["radius"] >> r;
        unsigned int axis;
        node["axis"] >> axis;
        Cylinder *cylinder = new Cylinder(pos, r, (Axis) axis);
        returnObject = cylinder;
    }

    if (returnObject) {
        // read the material and attach to object
        returnObject->material = parseMaterial(node["material"]);
    }

    return returnObject;
}

Light* Raytracer::parseLight(const YAML::Node& node) {
    Point position;
    node["position"] >> position;
    Color color;
    node["color"] >> color;
    return new Light(position, color);
}

/*
 * Read a scene from file
 */

bool shadowMode;
int maxRecursionDepth;

bool Raytracer::readScene(const std::string& inputFilename) {
    // Open file stream for reading and have the YAML module parse it
    std::ifstream fin(inputFilename.c_str());
    if (!fin) {
        cerr << "Error: unable to open " << inputFilename << " for reading." << endl;
        ;
        return false;
    }
    try {
        YAML::Parser parser(fin);
        if (parser) {
            YAML::Node doc;
            parser.GetNextDocument(doc);
            // Read scene configuration options
            // Here, we need judge if "RenderMode" exist, otherwise scene01.ymal will fail to interpret
            if (const YAML::Node * pMode = doc.FindValue("RenderMode")) {
                std::string configType;
                *pMode >> configType;
                std::cout << "Key 'RenderMode' exists, with value '" << configType << "'\n";
                if (configType == "zbuffer") {
                    scene = new ZBufferScene();
                } else if (configType == "normal") {
                    scene = new NormalBufferScene();
                } else {
                    scene = new PhongScene();
                }
            } else {
                std::cout << "Key 'RenderMode' doesn't exist, use Phong renderMode default\n";
                // Initialize a new scene anyway
                scene = new PhongScene();
            }

            // Judge if in shadow mode  Shadows: true
            if (const YAML::Node * pMode = doc.FindValue("Shadows")) {
                std::string configType;
                *pMode >> configType;
                std::cout << "Key 'Shadows' exists, with value '" << configType << "'\n";
                shadowMode = configType == "true";
            } else {
                std::cout << "Key 'Shadows' doesn't exist, use shadow default\n";
                // Initialize a new scene anyway
                shadowMode = true;
            }

            // Determine maxRecursionDepth, default: 2
            if (const YAML::Node * pMode = doc.FindValue("MaxRecursionDepth")) {
                std::string configType;
                *pMode >> maxRecursionDepth;
                std::cout << "Key 'MaxRecursionDepth' exists, with value '" << maxRecursionDepth << "'\n";
            } else {
                std::cout << "Key 'MaxRecursionDepth' doesn't exist, use default value of 2.\n";
                // Initialize a new scene anyway
                maxRecursionDepth = 2;
            }

            scene->setEye(parseTriple(doc["Eye"]));

            // Read and parse the scene objects
            const YAML::Node& sceneObjects = doc["Objects"];
            if (sceneObjects.GetType() != YAML::CT_SEQUENCE) {
                cerr << "Error: expected a sequence of objects." << endl;
                return false;
            }
            for (YAML::Iterator it = sceneObjects.begin(); it != sceneObjects.end(); ++it) {
                Object *obj = parseObject(*it);
                // Only add object if it is recognized
                if (obj) {
                    scene->addObject(obj);
                } else {
                    cerr << "Warning: found object of unknown type, ignored." << endl;
                }
            }

            // Read and parse light definitions
            const YAML::Node& sceneLights = doc["Lights"];
            if (sceneObjects.GetType() != YAML::CT_SEQUENCE) {
                cerr << "Error: expected a sequence of lights." << endl;
                return false;
            }
            for (YAML::Iterator it = sceneLights.begin(); it != sceneLights.end(); ++it) {
                scene->addLight(parseLight(*it));
            }
        }
        if (parser) {
            cerr << "Warning: unexpected YAML document, ignored." << endl;
        }
    } catch (YAML::ParserException& e) {
        std::cerr << "Error at line " << e.mark.line + 1 << ", col " << e.mark.column + 1 << ": " << e.msg << std::endl;
        return false;
    }

    cout << "YAML parsing results: " << scene->getNumObjects() << " objects read." << endl;
    return true;
}

void Raytracer::renderToFile(const std::string& outputFilename) {
    Image img(400, 400);
    cout << "Tracing..." << endl;
    scene->render(img);
    cout << "Writing image to " << outputFilename << "..." << endl;
    img.write_png(outputFilename.c_str());
    cout << "Done." << endl;
}
